package services;

import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.JavaType;
import org.codehaus.jackson.type.TypeReference;

import play.Logger;
import play.libs.F.Function;
import play.libs.F.Promise;
import play.libs.WS;
import play.mvc.Controller;

public class BaseService extends Controller {
	
	public final static ObjectMapper mapper = new ObjectMapper();
	
	public static Promise<WS.Response> post(final String url, final Object body, final Map<String,String> queryParams ){
		WS.WSRequestHolder wsrequest = WS.url(url);
		if(queryParams != null &&  !queryParams.isEmpty()){
			for(String queryKey : queryParams.keySet()){
				wsrequest.setQueryParameter(queryKey, queryParams.get(queryKey));
			}
		}
		try {
			return  wsrequest.setHeader(CONTENT_TYPE, "application/json").post(mapper.writeValueAsString(body));
		} catch (JsonGenerationException e) {
			Logger.error(e.getMessage());
		} catch (JsonMappingException e) {
			Logger.error(e.getMessage());
		} catch (IOException e) {
			Logger.error(e.getMessage());
		}
		return null;
	}
	
	public static Promise<WS.Response> put(final String url, final Object body, final Map<String,String> queryParams ){
		WS.WSRequestHolder wsrequest = WS.url(url);
		if(queryParams != null &&  !queryParams.isEmpty()){
			for(String queryKey : queryParams.keySet()){
				wsrequest.setQueryParameter(queryKey, queryParams.get(queryKey));
			}
		}
		try {
			return  wsrequest.setHeader(CONTENT_TYPE, "application/json").put(mapper.writeValueAsString(body));
		} catch (JsonGenerationException e) {
			Logger.error(e.getMessage());
		} catch (JsonMappingException e) {
			Logger.error(e.getMessage());
		} catch (IOException e) {
			Logger.error(e.getMessage());
		}
		return null;
	}
	
	public static Promise<WS.Response> get(final String url, final Map<String,String> queryParams ){
		WS.WSRequestHolder wsrequest = WS.url(url);
		if(queryParams != null &&  !queryParams.isEmpty()){
			for(String queryKey : queryParams.keySet()){
				wsrequest.setQueryParameter(queryKey, queryParams.get(queryKey));
			}
		}
		return  wsrequest.get();
	}
	
	public static Promise<WS.Response> delete(String url, Map<String,String> queryParams ){
		WS.WSRequestHolder wsrequest = WS.url(url);
		if(queryParams != null &&  !queryParams.isEmpty()){
			for(String queryKey : queryParams.keySet()){
				wsrequest.setQueryParameter(queryKey, queryParams.get(queryKey));
			}
		}
		return  wsrequest.setHeader(CONTENT_TYPE, "application/json").delete();
	}
	
	public static <T> Promise<T> get(final Class<T> type, final String url, final Map<String,String> queryParams ){
		return  get(url, queryParams).map(
		    new Function<WS.Response, T>() {
		        public T apply(WS.Response response) {
		        	T result = null;
		        	try {
		        		result = mapper.readValue(response.asJson(), type);
					} catch (JsonParseException e) {
						Logger.error("Error during json parsing",e);
					} catch (JsonMappingException e) {
						Logger.error("Error during json parsing",e);
					} catch (IOException e) {
						Logger.error("Error during json parsing",e);
					}
		        	return result;
		        }
		    }
		);
	}
	
	public static <T> Promise<T> get(final TypeReference<T> type, final String url, final Map<String,String> queryParams ){
		return  get(url, queryParams).map(
		    new Function<WS.Response, T>() {
		        public T apply(WS.Response response) {
		        	T result = null;
		        	try {
		        		result = mapper.readValue(response.asJson(),type);
					} catch (JsonParseException e) {
						Logger.error("Error during json parsing",e);
					} catch (JsonMappingException e) {
						Logger.error("Error during json parsing",e);
					} catch (IOException e) {
						Logger.error("Error during json parsing",e);
					}
		        	return result;
		        }
		    }
		);
	}
	
	
	public static <T> Promise<T> delete(final Class<T> type, final String url, final Map<String,String> queryParams ){
		return  delete(url, queryParams).map(
		    new Function<WS.Response, T>() {
		        public T apply(WS.Response response) {
		        	T result = null;
		        	try {
		        		result = mapper.readValue(response.asJson(), type);
					} catch (JsonParseException e) {
						Logger.error("Error during json parsing",e);
					} catch (JsonMappingException e) {
						Logger.error("Error during json parsing",e);
					} catch (IOException e) {
						Logger.error("Error during json parsing",e);
					}
		        	return result;
		        }
		    }
		);
	}
	
	public static <T> Promise<T> put(final Class<T> type, final String url, final Object body, final Map<String,String> queryParams ){
		
		return  put(url, body, queryParams).map(
		    new Function<WS.Response, T>() {
		        public T apply(WS.Response response) {
		        	T result = null;
		        	try {
		        		result = mapper.readValue(response.asJson(), type);
					} catch (JsonParseException e) {
						Logger.error("Error during json parsing",e);
					} catch (JsonMappingException e) {
						Logger.error("Error during json parsing",e);
					} catch (IOException e) {
						Logger.error("Error during json parsing",e);
					}
		        	return result;
		        }
		    }
		);
	}
	
public static <T> Promise<T> post(final Class<T> type, final String url, final Object body, final Map<String,String> queryParams ){
		
		return  put(url, body, queryParams).map(
		    new Function<WS.Response, T>() {
		        public T apply(WS.Response response) {
		        	T result = null;
		        	try {
		        		result = mapper.readValue(response.asJson(), type);
					} catch (JsonParseException e) {
						Logger.error("Error during json parsing",e);
					} catch (JsonMappingException e) {
						Logger.error("Error during json parsing",e);
					} catch (IOException e) {
						Logger.error("Error during json parsing",e);
					}
		        	return result;
		        }
		    }
		);
	}
	
	public static <T> Promise<List<T>> getCollection(final Class<T> type, final String url, final Map<String,String> queryParams ){
		return  get(url, queryParams).map(
		    new Function<WS.Response, List<T>>() {
		        public List<T> apply(WS.Response response) {
		        	List<T> result = null;
		        	try {
		        		if(response.asJson().isArray()){
		        			result = mapper.readValue(response.asJson(), mapper.getTypeFactory().constructCollectionType(ArrayList.class, type));
		        		} else {
		        			result = new ArrayList<T>();
		        			result.add(mapper.readValue(response.asJson(), type));
		        		}
		        	} catch (JsonParseException e) {
						Logger.error("Error during json parsing",e);
					} catch (JsonMappingException e) {
						Logger.error("Error during json parsing",e);
					} catch (IOException e) {
						Logger.error("Error during json parsing",e);
					}
		        	return result;
		        }
		    }
		);
	}
	
	
	public static <T,U> Promise<Map<T,U>> getMap(final Class<T> key, final Class<U> value, final String url, final Map<String,String> queryParams ){
		return  get(url, queryParams).map(
		    new Function<WS.Response, Map<T,U>>() {
		        public Map<T,U> apply(WS.Response response) {
		        	Map<T,U> result = null;
		        	try {
		        		result = mapper.readValue(response.asJson(), mapper.getTypeFactory().constructMapType(Map.class, key, value));
					} catch (JsonParseException e) {
						Logger.error("Error during json parsing",e);
					} catch (JsonMappingException e) {
						Logger.error("Error during json parsing",e);
					} catch (IOException e) {
						Logger.error("Error during json parsing",e);
					}
		        	return result;
		        }
		    }
		);
	}
	
	private static JavaType buildJavaType(Type target){
		JavaType result = null;
		if(ParameterizedType.class.isAssignableFrom(target.getClass())){
    		ParameterizedType paramType = (ParameterizedType)target;
    		Class<?> type = (Class<?>) paramType.getRawType();
    		if (Collection.class.isAssignableFrom(type)) {
    			return mapper.getTypeFactory().constructCollectionType((Class<? extends Collection>)type, buildJavaType(paramType.getActualTypeArguments()[0]));
            }
            if (Map.class.isAssignableFrom(type)) {
            	return mapper.getTypeFactory().constructMapType((Class<? extends Map>)type, buildJavaType(paramType.getActualTypeArguments()[0]), buildJavaType(paramType.getActualTypeArguments()[1]));
            }
            if (type.isArray()) {
            	return mapper.getTypeFactory().constructArrayType(type);
                        
            }
    	} 
    	return mapper.getTypeFactory().constructType(target);
	}
	 
}
